package utils_test

import (
	"testing"

	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"

	"github.com/grafana/grafana/pkg/apimachinery/utils"
)

type TestResource struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata
	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec Spec `json:"spec,omitempty"`
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TestResource) DeepCopyInto(out *TestResource) {
	*out = *in
	out.TypeMeta = in.TypeMeta
	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
	in.Spec.DeepCopyInto(&out.Spec)
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Playlist.
func (in *TestResource) DeepCopy() *TestResource {
	if in == nil {
		return nil
	}
	out := new(TestResource)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TestResource) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

// Spec defines model for Spec.
type Spec struct {
	// Name of the object.
	Title string `json:"title"`
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Spec) DeepCopyInto(out *Spec) {
	*out = *in
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Spec.
func (in *Spec) DeepCopy() *Spec {
	if in == nil {
		return nil
	}
	out := new(Spec)
	in.DeepCopyInto(out)
	return out
}

type TestResource2 struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata
	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec Spec2 `json:"spec,omitempty"`
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TestResource2) DeepCopyInto(out *TestResource2) {
	*out = *in
	out.TypeMeta = in.TypeMeta
	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
	in.Spec.DeepCopyInto(&out.Spec)
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Playlist.
func (in *TestResource2) DeepCopy() *TestResource2 {
	if in == nil {
		return nil
	}
	out := new(TestResource2)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TestResource2) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

// Spec defines model for Spec.
type Spec2 struct{}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Spec2) DeepCopyInto(out *Spec2) {
	*out = *in
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Spec.
func (in *Spec2) DeepCopy() *Spec2 {
	if in == nil {
		return nil
	}
	out := new(Spec2)
	in.DeepCopyInto(out)
	return out
}

func TestMetaAccessor(t *testing.T) {
	originInfo := &utils.ResourceOriginInfo{
		Name: "test",
		Path: "a/b/c",
		Hash: "kkk",
	}

	t.Run("fails for non resource objects", func(t *testing.T) {
		_, err := utils.MetaAccessor("hello")
		require.Error(t, err)

		_, err = utils.MetaAccessor(unstructured.Unstructured{})
		require.Error(t, err) // Not a pointer!

		_, err = utils.MetaAccessor(&unstructured.Unstructured{})
		require.NoError(t, err) // Must be a pointer

		_, err = utils.MetaAccessor(&TestResource{
			Spec: Spec{
				Title: "HELLO",
			},
		})
		require.NoError(t, err) // Must be a pointer
	})

	t.Run("get and set grafana metadata", func(t *testing.T) {
		res := &unstructured.Unstructured{}
		meta, err := utils.MetaAccessor(res)
		require.NoError(t, err)

		meta.SetOriginInfo(originInfo)
		meta.SetFolder("folderUID")

		require.Equal(t, map[string]string{
			"grafana.app/originName": "test",
			"grafana.app/originPath": "a/b/c",
			"grafana.app/originHash": "kkk",
			"grafana.app/folder":     "folderUID",
		}, res.GetAnnotations())

		meta.SetNamespace("aaa")
		meta.SetResourceVersionInt64(12345)
		require.Equal(t, "aaa", res.GetNamespace())
		require.Equal(t, "aaa", meta.GetNamespace())

		rv, err := meta.GetResourceVersionInt64()
		require.NoError(t, err)
		require.Equal(t, int64(12345), rv)
	})

	t.Run("blob info", func(t *testing.T) {
		info := &utils.BlobInfo{UID: "AAA", Size: 123, Hash: "xyz", MimeType: "application/json", Charset: "utf-8"}
		anno := info.String()
		require.Equal(t, "AAA; size=123; hash=xyz; mime=application/json; charset=utf-8", anno)
		copy := utils.ParseBlobInfo(anno)
		require.Equal(t, info, copy)
	})

	t.Run("find titles", func(t *testing.T) {
		// with a k8s object that has Spec.Title
		obj := &TestResource{
			TypeMeta: metav1.TypeMeta{
				Kind:       "TestKIND",
				APIVersion: "aaa/v1alpha2",
			},
			Spec: Spec{
				Title: "HELLO",
			},
		}

		meta, err := utils.MetaAccessor(obj)
		require.NoError(t, err)
		meta.SetOriginInfo(originInfo)
		meta.SetFolder("folderUID")

		require.Equal(t, map[string]string{
			"grafana.app/originName": "test",
			"grafana.app/originPath": "a/b/c",
			"grafana.app/originHash": "kkk",
			"grafana.app/folder":     "folderUID",
		}, obj.GetAnnotations())

		require.Equal(t, "HELLO", obj.Spec.Title)
		require.Equal(t, "HELLO", meta.FindTitle(""))
		obj.Spec.Title = ""
		require.Equal(t, "", meta.FindTitle("xxx"))

		gvk := meta.GetGroupVersionKind()
		require.Equal(t, "aaa/v1alpha2, Kind=TestKIND", gvk.String())

		// with a k8s object without Spec.Title
		obj2 := &TestResource2{}

		meta, err = utils.MetaAccessor(obj2)
		require.NoError(t, err)
		meta.SetOriginInfo(originInfo)
		meta.SetFolder("folderUID")

		require.Equal(t, map[string]string{
			"grafana.app/originName": "test",
			"grafana.app/originPath": "a/b/c",
			"grafana.app/originHash": "kkk",
			"grafana.app/folder":     "folderUID",
		}, obj2.GetAnnotations())

		require.Equal(t, "xxx", meta.FindTitle("xxx"))

		rt, ok := meta.GetRuntimeObject()
		require.Equal(t, obj2, rt)
		require.True(t, ok)

		spec, err := meta.GetSpec()
		require.Equal(t, obj2.Spec, spec)
		require.NoError(t, err)
	})
}
